/*
	The Raja Toolkit.
	Copyright (C) 2011 Raja-Toolkit Foundation

	This library is free software; you can redistribute it and/or
	modify it under the terms of the GNU Lesser General Public
	License as published by the Free Software Foundation; either
	version 3.0 of the License, or (at your option) any later version.

	This library is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
	Lesser General Public License for more details.

	You should have received a copy of the GNU Lesser General Public
	License along with this library; if not, write to the Free Software
	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/

#include "../program.hpp"

#include "gles2_internal.hpp"

#include "../sampler2.hpp"

#include <iostream>
#include <fstream>

#include <cstring>

using namespace std;
using namespace raja;
using namespace raja::math;

namespace raja
{
namespace render
{

///
/// print GLSL compiler log
///
void program::_print_log(uint32 obj)
{
	int infologLength = 0;
	int maxLength;

	if( glIsShader(obj) )
		glGetShaderiv(obj, GL_INFO_LOG_LENGTH, &maxLength);
	else
		glGetProgramiv(obj, GL_INFO_LOG_LENGTH, &maxLength);

	char infoLog[maxLength];

	if( glIsShader(obj) )
		glGetShaderInfoLog(obj, maxLength, &infologLength, infoLog);
	else
		glGetProgramInfoLog(obj, maxLength, &infologLength, infoLog);

	if (infologLength > 0)
		cout << infoLog << endl;

}

///////////////////////////////////////////////////////////////////////////////
// GLSL Attributes
///////////////////////////////////////////////////////////////////////////////
/*
/// translation table from GLSL attribute enum type to string
boost::unordered_map<GLenum, std::string>	_attrib_type_to_string;

///
/// fill in the glsl type to string translation table
///
bool	_init_attrib_type_to_string()
{
 _attrib_type_to_string[GL_FLOAT]		= "float";
 _attrib_type_to_string[GL_FLOAT_VEC2]	= "vec2";
 _attrib_type_to_string[GL_FLOAT_VEC3]	= "vec3";
 _attrib_type_to_string[GL_FLOAT_VEC4]	= "vec4";
 _attrib_type_to_string[GL_FLOAT_MAT2]	= "mat2";
 _attrib_type_to_string[GL_FLOAT_MAT3]	= "mat3";
 _attrib_type_to_string[GL_FLOAT_MAT4]	= "mat4";
 _attrib_type_to_string.rehash(_attrib_type_to_string.size());
 return true;
}

/// we need a dummy to init the table
bool	_dummy_init_of_attrib_type_name = _init_attrib_type_to_string();

///
/// get the name of the GLSL type
/// @param type GLSL type enum
/// @return name of the glsl attrib type
///
string get_name_of_attrib_type(GLenum type)
{
 if( _attrib_type_to_string.find(type) == _attrib_type_to_string.end() )
 {
  cout << "ERROR: Invalid GLSL type" << endl;
  return "";
 }

 return _attrib_type_to_string[type];
}
*/

static void _add_empty_constant(const_params &params, const std::string &name, uint32 id, GPU_DATA_TYPE t)
{
	cout << "adding " << name << endl;
	switch(t)
	{
	case GDT_REAL32:		///< a single float
		params.add_constant(name, id, real32(0.0f));
		break;

	case GDT_VEC2:		///< a 2d float vector
		params.add_constant(name, id, vec2());
		break;
	case GDT_VEC3:		///< a 3d float vector
		params.add_constant(name, id, vec3());
		break;
	case GDT_VEC4:		///< a 4d float vector
		params.add_constant(name, id, vec4());
		break;

	case GDT_SINT32:		///< a single int
		params.add_constant(name, id, sint32(0));
		break;
	case GDT_IVEC2:		///< a 2d int vector
		params.add_constant(name, id, ivec2());
		break;
	case GDT_IVEC3:		///< a 3d int vector
		params.add_constant(name, id, ivec3());
		break;
	case GDT_IVEC4:		///< a 4d int vector
		params.add_constant(name, id, ivec4());
		break;

	case GDT_BOOL:		///< a single bool
		params.add_constant(name, id, bool(false));
		break;
	case GDT_BVEC2:		///< a 2d bool vector
		params.add_constant(name, id, bvec2());
		break;
	case GDT_BVEC3:		///< a 3d bool vector
		params.add_constant(name, id, bvec3());
		break;
	case GDT_BVEC4:		///< a 4d bool vector
		params.add_constant(name, id, bvec4());
		break;

	case GDT_MAT2:		///< a 2d float matrix
		params.add_constant(name, id, mat2());
		break;
	case GDT_MAT3:		///< a 3d float matrix
		params.add_constant(name, id, mat3());
		break;
	case GDT_MAT4:		///< a 4d float matrix
		params.add_constant(name, id, mat4());
		break;

	case GDT_SAMPLER2:		///< a 2d texture
	{
		sampler2* s = 0;
		params.add_constant(name, id, s);
	}
		break;

	default:
		cout << "invalid constant type" << endl;
		assert(false);
	}
}

///////////////////////////////////////////////////////////////////////////////

///
/// create a shader program given vertex and fragment shaders
/// @param vs_str vertex shader text
/// @param fs_str fragment shader text
/// @return the created program. NULL means an error has occured
///
program*	program::create(const std::string &vs_str, const std::string &fs_str)
{
	if( fs_str.length() == 0 || vs_str.length() == 0 )
		return NULL;

	// create vertex and fragment shader
	GLuint vs	= glCreateShader(GL_VERTEX_SHADER);
	GLuint fs	= glCreateShader(GL_FRAGMENT_SHADER);

	// set the sources
	const char* vs_ss	= vs_str.c_str();
	const char* fs_ss	= fs_str.c_str();

	glShaderSource(vs, 1, &vs_ss, NULL);
	glShaderSource(fs, 1, &fs_ss, NULL);

	// compile shaders
	cout << "compiling vertex shader ";
	glCompileShader(vs);
	int vs_err;
	glGetShaderiv(vs, GL_COMPILE_STATUS, &vs_err);
	if( vs_err != GL_TRUE )
	{
		cout << endl;
		_print_log(vs);
		glDeleteShader(vs);
		glDeleteShader(fs);
		return NULL;
	}
	else
		cout << "no error" << endl;

	cout << "compiling fragment shader ";
	glCompileShader(fs);
	int fs_err;
	glGetShaderiv(fs, GL_COMPILE_STATUS, &fs_err);
	if( fs_err != GL_TRUE )
	{
		cout << endl;
		_print_log(fs);
		glDeleteShader(vs);
		glDeleteShader(fs);
		return NULL;
	}
	else
		cout << "no error" << endl;

	// create program
	GLuint prg	= glCreateProgram();
	glAttachShader(prg, vs);
	glAttachShader(prg, fs);
	cout << "linking program: ";
	glLinkProgram(prg);	// link vertex and fragment to the program
	int prg_err;
	glGetProgramiv(prg, GL_LINK_STATUS, &prg_err);
	if( prg_err != GL_TRUE )
	{
		cout << endl;
		_print_log(prg);
		glDeleteShader(vs);
		glDeleteShader(fs);
		glDeleteProgram(prg);
		return NULL;
	}
	else
		cout << "no error" << endl;

	extern GPU_DATA_TYPE gles_type_to_prog_type(GLenum type);

	// print the active attributes
	GLint active_attribs, attr_max_length;
	glGetProgramiv(prg, GL_ACTIVE_ATTRIBUTES, &active_attribs);
	glGetProgramiv(prg, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, &attr_max_length);
	char attr_name[attr_max_length + 1];
	for (GLint i = 0; i < active_attribs; i++)
	{
		GLint size;
		GLenum type;
		glGetActiveAttrib(prg, i, attr_max_length + 1, NULL, &size, &type, attr_name);
		cout << "attrib " << /*get_name_of_attrib_type(type)*/get_name_of_type(gles_type_to_prog_type(type)) << " "
			 << attr_name
			 << " is at location " << glGetAttribLocation(prg, attr_name)
			 << " has size " << size << endl;
	}

	// print the active uniforms
	GLint active_uniforms, uniform_max_length;
	glGetProgramiv(prg, GL_ACTIVE_UNIFORMS, &active_uniforms);
	glGetProgramiv(prg, GL_ACTIVE_UNIFORM_MAX_LENGTH, &uniform_max_length);
	char uni_name[uniform_max_length + 1];

	//uint32					sampler_slots	= 0;
	//	map< string, uniform >	uniforms;
	//uint32					data_offset		= 0;

	const_params	params;
	for (GLint i = 0; i < active_uniforms; i++)
	{
		GLint size;
		GLenum type;
		glGetActiveUniform(prg, i, uniform_max_length + 1, NULL, &size, &type, uni_name);
		/// @todo add support for arrays
		assert(size == 1); // no support for arrays yet



		_add_empty_constant(params, uni_name, glGetUniformLocation(prg, uni_name), gles_type_to_prog_type(type));
	}
	/*
 for( map< string, uniform >::iterator it = uniforms.begin(); it != uniforms.end(); it++ )
 {
  string name	= it->first;
  uniform	uni	= it->second;

  cout << "uniform " << get_name_of_type(uni.get_type()) << " " << uni.get_name() <<
	" has " << uni.get_array_size() << " elements, is at location " << uni.get_location() <<
	" on slot " << uni.get_slot() << endl;

 }
*/

	program*	prog	= new program();
	prog->__vs	= vs;
	prog->__fs	= fs;
	prog->__prg	= prg;
	prog->__const_params	= params;
	//	prog->__uniforms	= uniforms;
	//	prog->__uniform_cache			= new uint8[data_offset];
	//	memset(prog->__uniform_cache, 0, data_offset);
	//	prog->__uniform_cache_size	= data_offset;

	return prog;
}

///
/// destructor
///
program::~program()
{
	GLuint curr;
	glGetIntegerv(GL_CURRENT_PROGRAM, reinterpret_cast<GLint*>(&curr));
	if( curr == __prg )
	{
		cout << "~program: the program is currently in use, defaulting to 0" << endl;
		glUseProgram(0);
	}

	glDetachShader(__prg, __fs);
	glDetachShader(__prg, __vs);
	glDeleteShader(__vs);
	glDeleteShader(__fs);

	glDeleteProgram(__prg);
}

void program::use() const
{
	glUseProgram(__prg);
}


bool program::execute(const const_params &params) const
{
	glUseProgram(__prg);

	// set each parameter
	uint32		tex_slot	= 0;
	for( const_params::const_iterator it = params.begin(); it != params.end(); ++it )
	{
		const const_params::constant_info	ci = (*it).second;
		std::string name	= ci.__name;
		uint32 id			= ci.__id;

		switch( ci.__type )
		{
		case GDT_REAL32:		///< a single float
		{
			real32	v = val<real32>(params, name);
			glUniform1fv(id, 1, &v);
		}
			break;
		case GDT_VEC2:		///< a 2d float vector
		{
			const vec2	v = val<vec2>(params, name);
			glUniform2fv(id, 1, &v.x);
		}
			break;
		case GDT_VEC3:		///< a 3d float vector
		{
			const vec3	v = val<vec3>(params, name);
			glUniform3fv(id, 1, &v.x);
		}
			break;
		case GDT_VEC4:		///< a 4d float vector
		{
			vec4	v = val<vec4>(params, name);
			glUniform4fv(id, 1, &v.x);
		}
			break;

		case GDT_SINT32:		///< a single int
		{
			sint32	v = val<sint32>(params, name);
			glUniform1iv(id, 1, &v);
		}
			break;
		case GDT_IVEC2:		///< a 2d int vector
		{
			ivec2	v = val<ivec2>(params, name);
			glUniform2iv(id, 1, &v.x);
		}
			break;
		case GDT_IVEC3:		///< a 3d int vector
		{
			ivec3	v = val<ivec3>(params, name);
			glUniform3iv(id, 1, &v.x);
		}
			break;
		case GDT_IVEC4:		///< a 4d int vector
		{
			ivec4	v = val<ivec4>(params, name);
			glUniform4iv(id, 1, &v.x);
		}
			break;

		case GDT_BOOL:			///< a single bool
		{
			bool	v = val<bool>(params, name);
			glUniform1iv(id, 1, reinterpret_cast<sint32*>(&v));
		}
			break;
		case GDT_BVEC2:		///< a 2d bool vector
		{
			bvec2	v = val<bvec2>(params, name);
			glUniform2iv(id, 1, reinterpret_cast<sint32*>(&v.x));
		}
			break;
		case GDT_BVEC3:		///< a 3d bool vector
		{
			bvec3	v = val<bvec3>(params, name);
			glUniform3iv(id, 1, reinterpret_cast<sint32*>(&v.x));
		}
			break;
		case GDT_BVEC4:		///< a 4d bool vector
		{
			bvec4	v = val<bvec4>(params, name);
			glUniform4iv(id, 1, reinterpret_cast<sint32*>(&v.x));
		}
			break;

		case GDT_MAT2:		///< a 2d float matrix
		{
			mat2	v = val<mat2>(params, name);
			glUniformMatrix2fv(id, 1, GL_FALSE, &(v.col[0].x));
		}
			break;
		case GDT_MAT3:		///< a 3d float matrix
		{
			mat3	v = val<mat3>(params, name);
			glUniformMatrix3fv(id, 1, GL_FALSE, &(v.col[0].x));
		}
			break;
		case GDT_MAT4:		///< a 4d float matrix
		{
			mat4	v = val<mat4>(params, name);
			glUniformMatrix4fv(id, 1, GL_FALSE, &(v.col[0].x));
		}
			break;

		case GDT_SAMPLER2:		///< a 2d texture
		{
			sampler2_ptr	v = val<sampler2_ptr>(params, name);
			glUniform1i(id, tex_slot);
			glActiveTexture(GL_TEXTURE0 + (tex_slot++));
			glBindTexture(GL_TEXTURE_2D, v->get_tex_id());
		}
			break;

		default:
			cout << "invalid constant type" << endl;
			assert(false);
		}
	}

}

sint32 program::get_attrib_location(const std::string &attrib) const
{
	return glGetAttribLocation(__prg, attrib.c_str());
}

} // namespace render
} // namespace raja
